#!/usr/bin/env python
# -*- Mode: Python -*-
# -*- coding: utf-8 -*-

### --- ========= --- ###
### ---  imports  --- ###
### --- ========= --- ###
import os, sys
import getopt, atexit
import tty, termios
import operator
import rpm

from iniparse import INIConfig


### --- ======== --- ###
### ---  colors  --- ###
### --- ======== --- ###
BLACK = '\033[0;30m'
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[0;33m'
BLUE = '\033[0;34m'
MAGENTA = '\033[0;35m'
CYAN = '\033[0;36m'
WHITE = '\033[0;37m'
RESET = '\033[0;0m'


### --- ===================== --- ###
### ---  Display rpm status.  --- ###
### --- ===================== --- ###
def DisplayMessage(msgColor, msgText, msgEOL, msgSelector = False):

	msgIdentValue = 65
	msgStatusLen = 12

	msgIndent = '\033[' + str(msgIdentValue) + 'G\033[K\033[' + str(msgIdentValue) + 'G'

	if not Colorfull:
		msgColor = ''

	if msgSelector:
		msgOutput = RESET + msgIndent + msgText
	else:
		msgOutput = RESET + msgIndent + '[' + msgColor + msgText.center(msgStatusLen) + RESET + '\033[' + str(msgIdentValue + msgStatusLen) + 'G]'

	if msgEOL:
		print msgOutput

	else:
		print msgOutput,

	sys.stdout.flush()


### --- ======================================= --- ###
### ---  Get keyboard char without ENTER wait.  --- ###
### --- ======================================= --- ###
def ZGetChar():

	tfd = sys.stdin.fileno()
	sst = termios.tcgetattr(tfd)
	tty.setcbreak(tfd)
	ch = sys.stdin.read(1)
	tty.tcsetattr(tfd, 0, sst)
	return ch


### --- ================================ --- ###
### ---  Load list with protected rpms.  --- ### NTENEV
### --- ================================ --- ###
def LoadProtectedList(lstfile):
	try:
		plfd = open(lstfile)
		pl = plfd.readlines()
		plfd.close()
		return pl

	except IOError, (errno, strerror):

		if errno == 2:

                except IOError, (errno, strerror):
        
                        if errno == 2:
                                print 'Error: Unable to create pid file', pidfile
                
                        else:
                                print 'I/O error(%s): Unable to create/access pid file %s' % (errno, pidfile)
                        
                        sys.exit()


### --- ==================================== --- ###
### ---  Add package to list with protected  --- ###
### --- ==================================== --- ###
def pkgProtector(rpmHeaderObject):

	ProtectedList.append(rpmHeaderObject[rpm.RPMTAG_NAME] + '\n')
	ProtectedList.sort()
	plfd = open('protected.lst', 'w')
	plfd.writelines(ProtectedList)
	plfd.close()


### --- ========================= --- ###
### ---  Track process over rpm.  --- ###
### --- ========================= --- ###
def callbackZ(reason, amount, total, key, client_data):
	print '\b#',
	sys.stdout.flush()


### --- ================================= --- ###
### ---  Erase (or test to) selected rpm  --- ###
### --- ================================= --- ###
def pkgRemover(rpmHeaderObject, RealRun):

	tmpRTS = rpm.TransactionSet()

	if not RealRun:
		tmpRTS.setFlags(rpm.RPMTRANS_FLAG_TEST)

	tmpRTS.addErase(rpmHeaderObject[rpm.RPMTAG_NAME] + '-' + rpmHeaderObject[rpm.RPMTAG_VERSION] + '-' + rpmHeaderObject[rpm.RPMTAG_RELEASE])

	if not RealRun and len(tmpRTS.check()) > 0:
		return False

	elif RealRun:
		tmpRTS.order()
		tmpRTS.run(callbackZ,0)

	return True

 
### --- ================================================ --- ###
### ---  Check if rpm is in list of protected packages.  --- ###
### --- ================================================ --- ###
def CheckIsProtected(rpmHeaderObject):

	try:
		ProtectedList.index(rpmHeaderObject[rpm.RPMTAG_NAME] + '\n')
		return True

	except ValueError:
		return False


### --- ======================================== --- ###
### ---  Select rpm destiny by his removability  --- ###
### --- ======================================== --- ###
def DestinyMaker(rpmHeaderObject):

	if pkgRemover(rpmHeaderObject, False): ### --- True if rpm can be cleanly removed (have no deps).
		HaveDecision = False
		print rpmHeaderObject[rpm.RPMTAG_NAME] + '-' + rpmHeaderObject[rpm.RPMTAG_VERSION] + '-' + rpmHeaderObject[rpm.RPMTAG_RELEASE],
		DisplayMessage(RESET, 'Select [ (' + RED + 'R' + RESET + ')emove / (' + GREEN + 'P' + RESET + ')rotect / (' + YELLOW + 'S' + RESET + ')kip ] : ', False, True)

		while not HaveDecision:

			usrAnswer = ZGetChar()

			if usrAnswer == 'r' or usrAnswer == 'R': ### --- Remove rpm.
				DisplayMessage(RED, ' REMOVING : ', False)
				pkgRemover(rpmHeaderObject, True)
				HaveDecision = True
				DisplayMessage(RED, 'removed', True)

			elif usrAnswer == 'p' or usrAnswer == 'P': ### --- Insert rpm name into protected list.
				DisplayMessage(GREEN, 'PROTECTING :', False)
				pkgProtector(rpmHeaderObject)
				HaveDecision = True
				DisplayMessage(GREEN, 'protected', True)

			elif usrAnswer == 's' or usrAnswer == 'S': ### --- Do nothng and ask again in next iteration.
				HaveDecision = True
				DisplayMessage(YELLOW, 'skiped', True)

	else: ### --- Required by other rpm.
		if Verbosity:
			print rpmHeaderObject[rpm.RPMTAG_NAME] + '-' + rpmHeaderObject[rpm.RPMTAG_VERSION] + '-' + rpmHeaderObject[rpm.RPMTAG_RELEASE],
			DisplayMessage(RESET, 'required', True)


### --- ====================== --- ###
### ---  Iterate over rpm db.  --- ###
### --- ====================== --- ###
def rpmIteration():

	AllHeadersList = []

	MainTransactionSet = rpm.ts()

	for tmpLoadVar in MainTransactionSet.dbMatch(): ### --- Load database in list for realtime removing.
		AllHeadersList.append(tmpLoadVar)

	AllHeadersList.sort(key=operator.itemgetter(rpm.RPMTAG_NAME)) ### --- Maybe slow a little but look nice later

	print 'Installed rpm in system :',
	sys.stdout.flush()
	DisplayMessage(WHITE, str(len(AllHeadersList)), True)
	print '------------------------------------------------'
	sys.stdout.flush()

	for OneHeader in AllHeadersList:

		if Verbosity:
			print OneHeader[rpm.RPMTAG_NAME] + '-' + OneHeader[rpm.RPMTAG_VERSION] + '-' + OneHeader[rpm.RPMTAG_RELEASE],
			DisplayMessage(MAGENTA, 'TESTING ...', False)

		if CheckIsProtected(OneHeader): ### --- TRUE if rpm name exist in protected list.
			if not Verbosity:
				print OneHeader[rpm.RPMTAG_NAME] + '-' + OneHeader[rpm.RPMTAG_VERSION] + '-' + OneHeader[rpm.RPMTAG_RELEASE],

			DisplayMessage(GREEN, 'protected', True)

		else: ### --- Get decision by hand.
			DestinyMaker(OneHeader)



### --- ======================== --- ###
### ---  Display protected rpms  --- ###
### --- ======================== --- ###
def ListProtected():

	print 'List protected packages :\n'

	if not Verbosity:
		print 'RPMNAME \033[25G VERSION\n'
	else:
		print 'RPMNAME \033[25G VERSION \033[50G RELEASE\n'

	ts = rpm.ts()

	for opn in ProtectedList:
		mi = ts.dbMatch(rpm.RPMTAG_NAME, opn.strip('\n'))

		for hdr in mi:
			if not Verbosity:
				print hdr[rpm.RPMTAG_NAME], '\033[25G', hdr[rpm.RPMTAG_VERSION]
			else:
				print hdr[rpm.RPMTAG_NAME], '\033[25G', hdr[rpm.RPMTAG_VERSION], '\033[50G', hdr[rpm.RPMTAG_RELEASE]

	print 


### --- =========================================== --- ###
### ---  Remove some packages from protection list  --- ###
### --- =========================================== --- ###
def RemoveFromProtected(prpms):
	print 'Removing from protected list ...'

	for pkgname in prpms:
		try:
			ProtectedList.remove(pkgname + '\n')
			print pkgname,
			DisplayMessage(GREEN, 'removed', True)

		except ValueError:
			print pkgname,
			DisplayMessage(RED, 'NOT IN LIST', True)

	ProtectedList.sort()
	plfd = open('protected.lst', 'w')
	plfd.writelines(ProtectedList)
	plfd.close()


### --- =========================== --- ###
### ---  Display usage information  --- ###
### --- =========================== --- ###
def UsageInfo():

	print >> sys.stderr, '''
Usage: %s [OPTIONS]... [ACTION]...

OPTIONS :
 -n, --no-color         Do not display ANSI colors.
 -t, --no-clear         Do not clear screen before action.
 -v, --verbose          Be verbose in executions.

ACTION :
 -c, --check            Check installed rpms.
 -l, --list             List protected rpms.
 -h, --help             Display this information.
 -r, --remove <P1 P2>   Remove packages P1, P2 etc. from protected list.
                        Separate names with space.
     --version          Display version and exit.
''' % (sys.argv[0])


### --- ====================== --- ###
### ---  Display self version  --- ###
### --- ====================== --- ###
def DisplayVersion():

	print os.path.basename(sys.argv[0]), 'version 0.3.0'


def Exiter(pidfile):
    try:
        os.remove(pidfile)

    except IOError:
        print 'I/O error(%s) while removing pid file.'


def CheckPidFile(pidfile):

	if os.path.exists(pidfile):

		pidfd = open(pidfile, 'r')
		pidfromfile = pidfd.read()
		pidfd.close()

		try:
			os.getpgid(int(pidfromfile))
			print 'Another proces with pid', pidfromfile, 'currently is running'

		except OSError:
			print 'There is a pid file, but no process with pid', pidfromfile

		sys.exit()

	else:

		try:
			pidfd = open(pidfile, 'w')
			pidfd.write("%d" % os.getpid())
			pidfd.close()

		except IOError, (errno, strerror):

			if errno == 2:
				print 'Error: Unable to create pid file', pidfile

			else:
				print 'I/O error(%s): Unable to create/access pid file %s' % (errno, pidfile)

			sys.exit()


### --- ============== --- ###
### ---  Main Control  --- ###
### --- ============== --- ###
def main():

	### --- Check if I'm root.
	if os.geteuid() != 0:
	    print "You must be root to run this."
	    sys.exit()

	### --- Init and set some global vars.
	global ProtectedList
	global Colorfull
	global Verbosity

	ProtectedList = []
	Colorfull = True
	Verbosity = False

	ClearScreen = True
	PidFile = '/var/run/' + os.path.basename(sys.argv[0]) + '.pid'
	cfgFile = './rpmclean.cfg'


	### --- Parse command line arguments.
	try:
		opts, args = getopt.getopt(sys.argv[1:], 'chlnrtv', ['check', 'help', 'list', 'no-color', 'no-clear', 'remove', 'verbose', 'version'])

	except getopt.GetoptError, err:
		print str(err) ### --- Will print something like "option -? not recognized".
		print 'Try', sys.argv[0], '--help for more information.'
		sys.exit(2)


	### --- Set variables according CL arguments.
	for o, a in opts:

		if o in ('-n', '--no-color'):
			Colorfull = False

		elif o in ('-t', '--no-clear'):
			ClearScreen = False

		elif o in ('-v', '--verbose'):
			Verbosity = True


	### --- Execute coresponding actions.
	for o, a in opts:

		if o in ('-c', '--check'): ### --- Check for orphaned rpms and remove them if want.

			CheckPidFile(PidFile)
			atexit.register(Exiter, PidFile)

			rpm.setVerbosity(rpm.RPMLOG_ERR)

			MakeIteration = True

			cfgObj = INIConfig(open(cfgFile))

			ProtectedList = LoadProtectedList(cfgObj.files.plst)

			while MakeIteration:

				if ClearScreen:
					sys.stdout.write(os.popen('clear').read())
					sys.stdout.flush()

				rpmIteration()

				print '\nMake another iteration [ y / n ] : ',

				usrAnswer = ZGetChar()

				print '\n'

				if usrAnswer == 'y' or usrAnswer == 'Y':
					MakeIteration = True

				elif usrAnswer == 'n' or usrAnswer == 'N':
					MakeIteration = False

			break

		elif o in ('-h', '--help'): ### --- Display usage information.
			UsageInfo()
			break

		elif o in ('-l', '--list'): ### --- List protected packages.
			ProtectedList = LoadProtectedList(INIConfig(open(cfgFile)))
			ListProtected()
			break

		elif o in ('-r', '--remove'): ### --- Remove packages from protected list.

			if len(args) > 0:
				CheckPidFile(PidFile)
				atexit.register(Exiter, PidFile)
				ProtectedList = LoadProtectedList(INIConfig(open(cfgFile)))
				RemoveFromProtected(args)
			else:
				print 'Packages names missed !'
				print 'Try', sys.argv[0], '--help for more information.'
			break

		elif o in ( None, '--version'):
			DisplayVersion()
			break

	sys.exit()


### --- ================= --- ###
### --- ||  Main Flow  || --- ###
### --- ================= --- ###

if __name__ == "__main__":
    main()

#### --- Load config file.
#from iniparse import INIConfig
#cfg = INIConfig(open('rpmclean.cfg'))
#print cfg.files.plst



